/*
 * MIT License
 *
 * Copyright (c) 2023-present, tangli
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package tony.crypto.asymmetric

import java.security.KeyPair
import tony.core.exception.ApiException

/**
 * SM2 非对称加密算法实现
 *
 * SM2是中国国家密码管理局发布的椭圆曲线公钥密码算法，具有以下特点：
 * - 基于椭圆曲线密码学（ECC）
 * - 密文格式：C1C3C2（C1是椭圆曲线点，C3是摘要，C2是密文数据）
 * - 签名算法：SM3withSM2
 * - 公钥格式：04 开头的未压缩格式
 * - 符合GM/T 0003.2-2012 标准
 *
 * @author tangli
 * @date 2025/01/22 20:00
 */
public object Sm2 : AsymmetricCrypto {
    /**
     * SM2算法实例，使用C1C3C2格式
     */
    private val sm2Instance: Sm2Instance by lazy {
        Sm2Instance()
    }

    /**
     * 生成SM2密钥对
     *
     * @return [KeyPair] SM2密钥对
     * @throws Exception 密钥生成失败时抛出异常
     */
    override fun generateKeyPair(): KeyPair =
        try {
            sm2Instance.generateKeyPair()
        } catch (e: Exception) {
            throw ApiException("Failed to generate SM2 key pair", e)
        }

    /**
     * SM2加密
     *
     * 使用公钥对明文进行加密，输出C1C3C2格式的密文
     *
     * @param [src] 待加密的明文数据
     * @param [secret] 公钥的字节数组表示（X.509 格式）
     * @return [ByteArray] 加密后的密文数据
     * @throws Exception 加密失败时抛出异常
     */
    override fun encrypt(
        src: ByteArray,
        secret: ByteArray,
    ): ByteArray {
        if (src.isEmpty()) {
            throw IllegalArgumentException("Source data cannot be empty")
        }

        return try {
            sm2Instance.encrypt(src, secret)
        } catch (e: Exception) {
            throw ApiException("Failed to encrypt with SM2", e)
        }
    }

    /**
     * SM2解密
     *
     * 使用私钥对C1C3C2格式的密文进行解密
     *
     * @param [src] 待解密的密文数据
     * @param [secret] 私钥的字节数组表示（PKCS#8 格式）
     * @return [ByteArray] 解密后的明文数据
     * @throws Exception 解密失败时抛出异常
     */
    override fun decrypt(
        src: ByteArray,
        secret: ByteArray,
    ): ByteArray {
        if (src.isEmpty()) {
            throw IllegalArgumentException("Source data cannot be empty")
        }

        return try {
            sm2Instance.decrypt(src, secret)
        } catch (e: Exception) {
            throw ApiException("Failed to decrypt with SM2", e)
        }
    }

    /**
     * SM2数字签名
     *
     * 使用私钥对数据进行数字签名
     *
     * @param [src] 待签名的原始数据
     * @param [privateKey] 私钥的字节数组表示（PKCS#8 格式）
     * @return [ByteArray] 数字签名结果
     * @throws Exception 签名失败时抛出异常
     */
    override fun sign(
        src: ByteArray,
        privateKey: ByteArray,
    ): ByteArray {
        if (src.isEmpty()) {
            return ByteArray(0)
        }

        return try {
            sm2Instance.sign(src, privateKey)
        } catch (e: Exception) {
            throw ApiException("Failed to sign with SM2", e)
        }
    }

    /**
     * SM2签名验证
     *
     * 使用公钥验证数字签名的有效性
     *
     * @param [src] 原始数据
     * @param [signature] 数字签名数据
     * @param [publicKey] 公钥的字节数组表示（X.509 格式）
     * @return [Boolean] 验签结果，true表示验签成功，false表示验签失败
     */
    override fun verify(
        src: ByteArray,
        signature: ByteArray,
        publicKey: ByteArray,
    ): Boolean {
        // 如果原始数据和签名都为空，则认为验证成功
        if (src.isEmpty() && signature.isEmpty()) {
            return true
        }

        // 如果只有其中一个为空，则验证失败
        if (src.isEmpty() || signature.isEmpty()) {
            return false
        }

        return try {
            sm2Instance.verify(src, signature, publicKey)
        } catch (_: Exception) {
            false
        }
    }
}
